package cn.highedu.aspect;


import cn.highedu.entity.User;
import cn.highedu.exception.IllegalParameterException;
import cn.highedu.exception.PasswordErrorException;
import cn.highedu.service.UserService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Aspect 来自 AspectJ, Spring 支持这个注解的功能
 * 用于定义切面儿功能组件， 必须和@Component或者@Bean联合使用
 */
@Component
@Aspect
public class LoginAspect {
    Logger logger = LoggerFactory.getLogger(LoginAspect.class);

    @Autowired
    private UserService userService;

    @After("execution(* cn.highedu.service.UserService.login(..))")
    public void loginLog(JoinPoint joinPoint){
        Signature method = joinPoint.getSignature();
        logger.debug("执行了登录方法{}", method);
    }

    /**
     * 记录登录方法的调用参数，如果是admin用户，就抛出异常
     * @param joinPoint 连接点
     */
    @Before("execution(* cn.highedu.service.UserService.login(..))")
    public void loginStart(JoinPoint joinPoint){
        Signature method = joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        logger.debug("执行了登录方法{}，参数{}", method, args);
        // 限制Admin用户不能登录
        String username = (String) args[0];
        if ("admin".equals(username)){
            throw new IllegalParameterException("Admin用户不能登录");
        }
    }

    /**
     * @AfterReturning 方法在login方法之后执行，如果login方法正常结束，就执行
     * 登录后，清除用户的密码，保证安全
     * @param joinPoint 连接点
     * @param result login方法的返回值
     */
    @AfterReturning(value = "execution(* cn.highedu.service.UserService.login(..))",
            returning = "result")
    public void loginEnd(JoinPoint joinPoint, User result){
        Signature method = joinPoint.getSignature();
        logger.debug("执行了登录方法{}，返回值{}", method, result);
        // 清除用户的密码，提升安全性
        result.setPassword(null);
    }

    /**
     *  afterThrowing 会在login方法出现异常以后执行，
     *  记录失败次数
     *  e 就是login方法抛出的异常对象
     */
    @AfterThrowing(value = "execution(* cn.highedu.service.UserService.login(..))",
            throwing = "e")
    public void loginException(JoinPoint joinPoint, Throwable e){
        Signature method = joinPoint.getSignature();
        logger.debug("执行了登录方法{}，异常{}", method, e);
        // 记录失败次数
        if (e instanceof PasswordErrorException){
            String username = (String) joinPoint.getArgs()[0];
            userService.incLoginFailCount(username);
            // 如果失败次数超过5次，就锁定用户，并且抛出异常
            User user = userService.getByUsername(username);
            if (user.getLoginFailCount() >= 5){
                userService.lockUser(username);
                throw new RuntimeException("登录失败次数超过5次，用户已被锁定");
            }
        }
    }

    /**
     * @Around 环绕通知，可以在方法执行之前和之后执行代码
     * 1. 必须有返回值，返回值就是目标方法的返回值
     * 2. 必须有参数，参数就是ProceedingJoinPoint
     * 3. 必须抛出异常
     * 4. 必须调用ProceedingJoinPoint的proceed()方法
     * 5. 可以修改目标方法的返回值
     * 6. 可以修改目标方法的参数
     * 7. 可以修改目标方法的执行逻辑
     * 8. 可以吞没异常，或者改变异常的类型
     * 这里的环绕通知，用于记录登录方法的执行时间，如果执行时间超过1秒，就记录日志
     * @param joinPoint 连接点
     * @return login方法的返回值
     * @throws Throwable
     */
    @Around("execution(* cn.highedu.service.UserService.login(..))")
    public Object loginAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature method = joinPoint.getSignature();
        logger.debug("执行了登录方法{}，参数{}", method, joinPoint.getArgs());
        long start = System.currentTimeMillis();
        // 执行目标方法 login
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        // 计算执行时间
        long duration = end - start;
        logger.debug("登录方法{}，执行时间{}", method, duration);
        // 如果执行时间超过1秒，就记录日志
        if (duration > 1000){
            logger.warn("登录方法{}，执行时间{}，超过1秒", method, duration);
        }
        return result; // 返回目标方法的返回值
    }
}









